﻿using System.Linq.Expressions;

using zijian666.Core;
using zijian666.Core.Abstractions;

namespace zijian666.Data;

/// <summary>
/// 实体类构造器
/// </summary>
public static class EntityBuilder
{
    // IEntityBuilder<T> 缓存
    private static readonly ConcurrentDictionary<Type, IEntityBuilder<object>> CACHED_BUILDER = new();

    /// <summary>
    /// 获取指定实体类型的构造器
    /// </summary>
    public static IEntityBuilder<T> GetBuilder<T>()
        => (IEntityBuilder<T>)CACHED_BUILDER.GetOrAdd(typeof(T), t => CreateBuilder<T>());


    private static IEntityBuilder<object> CreateBuilder<T>()
    {
        if (typeof(T).IsSimpleType())
        {
            return (IEntityBuilder<object>)new SimpleValueBuilder<T>();
        }
        var ctor = typeof(T).GetConstructor(FixedValues.TYPES_IDATARECORD);
        if (ctor != null)
        {
            return (IEntityBuilder<object>)new ConstructorBuilder<T>(ctor);
        }
        foreach (var c in typeof(T).GetConstructors())
        {
            var p = c.GetParameters();
            if (p.Length == 1 && typeof(IDataRecord).IsAssignableFrom(p[0].ParameterType))
            {
                return (IEntityBuilder<object>)new ConstructorBuilder<T>(c);
            }
        }
        return (IEntityBuilder<object>)new StandardBuilder<T>();
    }

    private class ConstructorBuilder<T> : IEntityBuilder<T>
    {
        private readonly Func<IDataRecord, T> _constructor;
        public ConstructorBuilder(ConstructorInfo constructor)
        {
            var p1 = Expression.Parameter(typeof(IDataRecord));
            var @new = Expression.New(constructor, p1);
            var lambda = Expression.Lambda(@new, p1);
            _constructor = (Func<IDataRecord, T>)lambda.Compile();
        }

        public T ToSingle(IDataRecord record) => _constructor(record);
    }

    private class SimpleValueBuilder<T> : IEntityBuilder<T>
    {
        Func<IDataRecord, int, T> _reader;
        public SimpleValueBuilder()
        {
            _reader = (Func<IDataRecord, int, T>)GetDataRecordReader(typeof(T));
        }

        public T ToSingle(IDataRecord record)
        {
            return _reader(record, 0);
        }
    }

    private class StandardBuilder<T> : IEntityBuilder<T>
    {
        public StandardBuilder()
        {
            _properties = typeof(T).GetProperties()
                .Where(p => p.CanWrite && p.GetIndexParameters().Length == 0)
                .Select(p => new Property<T>(p))
                .ToArray();
            _propertyCount = _properties.Length;
        }


        private readonly Property<T>[] _properties;
        private readonly int _propertyCount;

        private Property<T> GetProperty(string columnName)
        {
            for (var j = 0; j < _propertyCount; j++)
            {
                var p = _properties[j];
                if (p.StrictMatch(columnName))
                {
                    return p;
                }
            }
            for (var j = 0; j < _propertyCount; j++)
            {
                var p = _properties[j];
                if (p.FuzzyMatch(columnName))
                {
                    return p;
                }
            }
            return null;
        }

        public T ToSingle(IDataRecord record)
        {
            var entity = Activator.CreateInstance<T>();
            for (var i = record.FieldCount - 1; i >= 0; i--)
            {
                var columnName = record.GetName(i);
                var p = GetProperty(columnName);
                if (p != null)
                {
                    if (p.IsNullable && record.IsDBNull(i))
                    {
                        p.SetNull(entity);
                    }
                    else
                    {
                        p.Fill(entity, record, i);
                    }
                }
            }
            return entity;
        }

        public IEnumerable<T> ToMultiple(IDataReader reader)
        {
            if (!reader.Read())
            {
                yield break;
            }

            var props = new List<(int, Property<T>)>();
            for (var i = reader.FieldCount - 1; i >= 0; i--)
            {
                var columnName = reader.GetName(i);
                var p = GetProperty(columnName);
                if (p != null)
                {
                    props.Add((i, p));
                }
            }
            var propCount = props.Count;
            do
            {
                var entity = Activator.CreateInstance<T>();
                for (var j = 0; j < propCount; j++)
                {
                    var (i, p) = props[j];
                    if (p.IsNullable && reader.IsDBNull(i))
                    {
                        p.SetNull(entity);
                    }
                    else
                    {
                        p.Fill(entity, reader, i);
                    }
                }
                yield return entity;
            } while (reader.Read());
        }
    }

    private class Property<T>
    {
        private readonly PropertyInfo _property;
        private readonly ColumnAttribute? _column;
        public Property(PropertyInfo property)
        {
            var reader = GetDataRecordReader(property.PropertyType);
            var p1 = Expression.Parameter(typeof(T));
            var p2 = Expression.Parameter(typeof(IDataRecord));
            var p3 = Expression.Parameter(typeof(int));
            Expression call = Expression.Invoke(Expression.Constant(reader), p2, p3);
            call = Expression.Call(p1, property.SetMethod!, call);
            Fill = (Action<T, IDataRecord, int>)Expression.Lambda(call, p1, p2, p3).Compile();
            _property = property;
            IsNullable = !property.PropertyType.IsValueType || Nullable.GetUnderlyingType(property.PropertyType) is not null;
            _column = (property.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute);
            if (IsNullable)
            {
                p1 = Expression.Parameter(typeof(T));
                var nul = Expression.Convert(Expression.Constant(null), property.PropertyType);
                call = Expression.Call(p1, property.SetMethod!, nul);
                SetNull = (Action<T>)Expression.Lambda(call, p1).Compile();
            }
        }

        [MemberNotNullWhen(true, nameof(SetNull))]
        public bool IsNullable { get; }

        public bool StrictMatch(string columnName)
        {
            if (!string.IsNullOrWhiteSpace(_column?.Name))
            {
                return _column.Name == columnName;
            }
            return _property.Name == columnName;
        }

        public bool FuzzyMatch(string columnName)
        {
            if (!string.IsNullOrWhiteSpace(_column?.Name) && _column.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
            if (_property.Name.Equals(columnName, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
            if (columnName.IndexOfAny(['_', '-']) > -1)
            {
                return FuzzyMatch(columnName.Replace("_", "").Replace("-", ""));
            }
            return false;
        }

        public Action<T, IDataRecord, int> Fill { get; }

        public Action<T>? SetNull { get; }
    }

    // Func<IDataRecord, int, T> 缓存
    private static readonly ConcurrentDictionary<Type, Delegate> CACHED_READER = new();

    /// <summary>
    /// 读取 <paramref name="record"/> 中指定列的值
    /// </summary>
    public static T Read<T>(this IDataRecord record, int columnIndex)
    {
        var reader = (Func<IDataRecord, int, T>)GetDataRecordReader(typeof(T));
        return reader(record, columnIndex);
    }


    private static Delegate GetDataRecordReader(Type type)
    {
        return CACHED_READER.GetOrAdd(type, t => BuildDataRecordReader(t).Compile());
    }
    /// <summary>   
    /// 创建从 IDataRecord 读取指定类型值的表达式树
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>

    private static LambdaExpression BuildDataRecordReader(Type type)
    {
        var realType = Nullable.GetUnderlyingType(type) ?? type;

        if (DataSerializer(realType) is { } fromDbValue)
        {
            var reader0 = Expression.Parameter(typeof(IDataRecord));
            var index0 = Expression.Parameter(typeof(int));
            var argType = fromDbValue.GetParameters()[0].ParameterType;
            var getValue0 = DataRecordGetValue(argType) ?? typeof(IDataRecord).GetMethod("GetValue", FixedValues.TYPES_INT32)!;
            Expression value0 = Expression.Call(reader0, getValue0, index0);
            if (argType != getValue0.ReturnType)
            {
                value0 = Expression.Convert(value0, argType);
            }
            Expression ret0 = Expression.Call(fromDbValue, value0, Expression.Constant(realType));
            if (type != typeof(object))
            {
                ret0 = Expression.Convert(ret0, type);
            }
            return Expression.Lambda(ret0, reader0, index0);
        }

        var method = DataRecordGetValue(realType);
        if (method != null)
        {
            return CreateDataRecordGetMethod(method, type);
        }

        var getValue = typeof(IDataRecord).GetMethod("GetValue", FixedValues.TYPES_INT32)!;
        var reader = Expression.Parameter(typeof(IDataRecord));
        var index = Expression.Parameter(typeof(int));
        var value = Expression.Call(reader, getValue, index);

        var changeType = ((Func<object?, Type, object?>)ChangeType);
        Expression ret = Expression.Invoke(Expression.Constant(changeType), value, Expression.Constant(realType));
        if (type != typeof(object))
        {
            ret = Expression.Convert(ret, type);
        }
        return Expression.Lambda(ret, reader, index);


        static MethodInfo? DataSerializer(Type type)
        {
            var interfaces = type.GetInterfaces();
            foreach (var iface in interfaces)
            {
                if (iface != typeof(IDbDataSerializer))
                {
                    continue;
                }
                var method = type.GetInterfaceMap(iface).TargetMethods.FirstOrDefault(m => m.IsStatic);
                if (method is not null)
                {
                    return method;
                }
                return type.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static, null, [typeof(object), typeof(Type)], null);
            }
            return null;
        }
    }

    private static MethodInfo? DataRecordGetValue(Type type)
    {
        var typeCode = Type.GetTypeCode(type);
        switch (typeCode)
        {
            case TypeCode.Boolean:
            case TypeCode.Byte:
            case TypeCode.Char:
            case TypeCode.DateTime:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.String:
                return typeof(IDataRecord).GetMethod("Get" + typeCode.ToString(), FixedValues.TYPES_INT32);
            case TypeCode.DBNull:
            case TypeCode.Empty:
                return null;
            case TypeCode.Int16:
            case TypeCode.SByte:
            case TypeCode.Single:
            case TypeCode.UInt16:
                return typeof(IDataRecord).GetMethod("GetInt32", FixedValues.TYPES_INT32);
            case TypeCode.UInt32:
                return typeof(IDataRecord).GetMethod("GetInt64", FixedValues.TYPES_INT32);
            default:
                if (type == typeof(Guid))
                {
                    return typeof(IDataRecord).GetMethod("GetGuid", FixedValues.TYPES_INT32);
                }
                return null;
        }
    }

    private static object? ChangeType(object? value, Type conversionType)
    {
        if (value is null || value is DBValue)
        {
            return null;
        }
        if (FeatureManager.Get<IFormatterConverterFeature>() is { } format)
        {
            if (format.TryConvert(value, conversionType, out var result))
            {
                return result;
            }
            throw new InvalidCastException($"无法将类型 {value?.GetType().FullName ?? "null"} 转换为 {conversionType.FullName} 。");
        }
        return Convert.ChangeType(value, conversionType);
    }

    /// <summary>
    /// 创建 IDataRecord 的 GetXXX 方法的表达式树
    /// </summary>
    /// <param name="methodName"></param>
    /// <param name="returnType"></param>
    /// <returns></returns>
    private static LambdaExpression CreateDataRecordGetMethod(MethodInfo method, Type returnType)
    {
        var p1 = Expression.Parameter(typeof(IDataRecord));
        var p2 = Expression.Parameter(typeof(int));
        Expression ret = Expression.Call(p1, method, p2);
        if (returnType != method.ReturnType)
        {
            ret = Expression.Convert(ret, returnType);
        }
        return Expression.Lambda(ret, p1, p2);
    }

}
